# -*- coding: UTF-8 -*-

import neo4jtest
import math
import random
import copy


class StraightCosineSim():
    def __init__(self, k=10):
        self.kg = neo4jtest.MashupKnowledgeGraph("neo4j://localhost:7687", "neo4j", "hahaha")
        self.apis = {}
        self.api_count = {}
        for api in self.kg.get_api_ids():
            self.apis[api] = True
        self.mashups = {}
        self.mashup_apis = {}
        for mashup in self.kg.get_mashup_ids():
            in_nodes = self.kg.get_in_node_by_id(node_id=mashup)
            self.mashup_apis[mashup] = [in_node for in_node in in_nodes if in_node in self.apis]
            if len(self.mashup_apis[mashup]) > 0:
                self.mashups[mashup] = in_nodes
                for api_id in self.mashup_apis[mashup]:
                    if api_id in self.api_count:
                        self.api_count[api_id] += 1
                    else:
                        self.api_count[api_id] = 1
        self.mashup_ids = list(self.mashups.keys())
        self.api_pop = sorted(self.api_count.items(), key=lambda item: -item[1])

        self.idcg = {1:1.0}
        for i in range(2, 100):
            self.idcg[i] = self.idcg[i - 1] + 1.0 / math.log2(i + 1)

        self.k = k
        self.splits = self.divide_k()

    def calc_sim(self, cur_m, tar_m):
        arr_tar = self.mashups[tar_m]
        cnt_tar = len(arr_tar)

        arr_cur = self.mashups[cur_m]
        cnt_cur = 0
        cnt_cross = 0
        for item in arr_cur:
            if item in self.apis:
                continue
            cnt_cur += 1
            if item in arr_tar:
                cnt_cross += 1

        return 1.0 * cnt_cross / math.sqrt(cnt_cur * cnt_tar)

    def recommend(self, cur_m, candidates, ma_topn=10, api_topn=10):
        # sims_mashup = {}
        # for tar_m in candidates:
        #     if tar_m == cur_m:
        #         continue
        #     sims_mashup[tar_m] = self.calc_sim(cur_m=cur_m, tar_m=tar_m)
        # topn_mashup = sorted(sims_mashup.items(), key=lambda item: -item[1])[:ma_topn]
        # sims_api = {}
        # for ma in topn_mashup:
        #     for api in self.mashup_apis[ma[0]]:
        #         if api in sims_api:
        #             sims_api[api] += 1
        #         else:
        #             sims_api[api] = 1
        # topn_api = sorted(sims_api.items(), key=lambda item: -item[1])[:api_topn]
        # return [item[0] for item in topn_api]

        return [item[0] for item in self.api_pop[:api_topn]]

    def test(self, ma_topn=10, api_topn=10):
        # print('test start')
        res = 0.0
        splits = copy.deepcopy(self.splits)
        # print('divide complete')
        for i in range(self.k):
            # print('start test split', i)
            test_set = splits[i]
            train_set = set()
            for j in range(self.k):
                if j != i:
                    train_set |= splits[j]
            
            score = 0.0
            for cur_m in test_set:
                actually = self.mashup_apis[cur_m]
                recommend = self.recommend(cur_m=cur_m, candidates=train_set, ma_topn=ma_topn, api_topn=api_topn)
                score += self.evaluate(recommend_result=recommend, actually_result=actually)
            res += score / len(test_set)
            # print('end test split', i)
            
        return res / self.k

    def divide_k(self):
        one_split_count = math.floor(len(self.mashup_ids) / self.k)
        cur = set(self.mashup_ids)
        res = []
        for i in range(self.k - 1):
            temp = set(random.sample(cur, one_split_count))
            cur -= temp
            res.append(temp)
        res.append(cur)
        return res

    def evaluate(self, recommend_result, actually_result):
        # cross = set(recommend_result) & set(actually_result)
        # return 1.0 * len(cross) / len(actually_result)

        
        idcg = self.idcg[len(actually_result)]
        act_set = set(actually_result)
        dcg = 0.0
        for i in range(len(recommend_result)):
            if recommend_result[i] in act_set:
                dcg += 1.0 / math.log2(i + 2)
        return dcg / idcg


        

if __name__ == "__main__":
    scs = StraightCosineSim(k=10)
    for i in range(9):
        topn = i * 5 + 10
        print('top', topn, scs.test(ma_topn=50, api_topn=topn))
    